//----------------------------------------------------------------------------
// M A R I T I M E  R E S E A R C H  I N S T I T U T E  N E T H E R L A N D S
//----------------------------------------------------------------------------
// Copyright (C) 2009 - MARIN - All rights reserved - http://www.marin.nl
//----------------------------------------------------------------------------
// Program    : various
// Module     : various
// File       : OsgViewer.cpp
//----------------------------------------------------------------------------

#ifdef _WIN32
#include <Windows.h> // TODO compiles but does not work in Windows
#endif
#include <iostream> // for printSceneGraph
#include <osg/Geometry>
#include <osg/Material>
#include <osg/MatrixTransform>
#include <osg/PositionAttitudeTransform>
#include <osg/ShapeDrawable>
#include <osg/Transform>
#include <osgDB/WriteFile>
#include <osgDB/ReadFile>
#include <osgText/Text>
#include <osgGA/TrackballManipulator>
#include <osgGA/StateSetManipulator>
#include <osgViewer/ViewerEventHandlers>
#include "OsgViewer.h"
#include "assertplus.h"


// initial size of viewer window
#define WIDTH  800
#define HEIGHT 600

// file to save current viewpoint in
#define viewfile "OsgViewer.dat"

// shape names
#define SPHERE    "sphere"
#define BOX       "box"
#define CONE      "cone"
#define CYLINDER  "cylinder"
#define CAPSULE   "capsule"
#define RECTANGLE "rectangle"
#define POLYGONE  "polygon"

// color names
#define BLACK     "black"
#define WHITE     "white"
#define YELLOW    "yellow"
#define MAGENTA   "magenta"
#define CYAN      "cyan"
#define GREEN     "green"
#define BLUE      "blue"
#define RED       "red"
#define ORANGE    "orange"
#define GREY      "grey"

// lists of shapes and colors
typedef std::vector<std::string>            SHAPES; ///< list of shape names
typedef std::map   <std::string, osg::Vec4> COLORS; ///< list of color names
static SHAPES shapes;                               ///< list of shape names
static COLORS colors;                               ///< list of color names


// TODO move microsleep to some more general file!
// -----------------------------------------------------------------------------
// function microsleep
// -----------------------------------------------------------------------------
/// general function to delay system a bit (handy for event loops)
/** Usage example:
 *  while (!done)
 *  {
 *      microsleep(50000); // 0.05 seconds delay to conserve processor time
 *      pAreaWindow->handleEvents(); // via callback can set 'done' to true
 *      pPlotter->plot();
 *  }
 */
// -----------------------------------------------------------------------------
#ifdef _WIN32
void microsleep(long microseconds)
{
    Sleep((long)(microseconds / 1000.0 + 0.5));
}
#else
void microsleep(long microseconds)
{
    timeval delay;
    delay.tv_sec  = microseconds / 1000000L;
    delay.tv_usec = microseconds % 1000000L;
    select(0, 0, 0, 0, &delay);
}
#endif


// --- helper functions --------------------------------------------------------

//----------------------------------------------------------------------------
// function printSceneGraph
//----------------------------------------------------------------------------
/// debugging function to display scenegraph
//----------------------------------------------------------------------------
static void printSceneGraph(osg::Node *pNode, ///< start node of tree
                            int level)        ///< how deep to descend tree
{
    if (!pNode)
    {
        return;
    }

    std::string c = pNode->className();
    std::string n = pNode->getName();
    for (int i = 0; i < level; ++i)
    {
        std::cout << " ";
    }
    std::cout << pNode << " " << c << " (" << n << ")" << std::endl;

    osg::Geode *pGeode = dynamic_cast<osg::Geode*>(pNode);
    if (pGeode)
    {
        for (unsigned int i=0; i<pGeode->getNumDrawables(); i++)
        {
            osg::Drawable *pDrawable = pGeode->getDrawable(i);
            for (int i = 0; i <= level; ++i)
            {
                std::cout << " ";
            }
            std::cout << pDrawable << " " << pDrawable->className() << std::endl;
        }
    }

    // recursive call
    osg::Group *pGroup = dynamic_cast<osg::Group*>(pNode);
    if (pGroup)
    {
        for (unsigned int i = 0; i < pGroup->getNumChildren(); i++)
        {
            printSceneGraph(pGroup->getChild(i), level + 1);
        }
    }
}


//----------------------------------------------------------------------------
// function getColor
//----------------------------------------------------------------------------
/** @return color as rgb value */
//----------------------------------------------------------------------------
static osg::Vec4 getColor(const std::string& color) ///<  standard color name or "r;g;b" with r,g,b between 0 and 1
{
    COLORS::iterator iColor = colors.find(color);
    if (iColor != colors.end())
    {
        return iColor->second;
    }
    size_t one = color.find(';');
    size_t two = color.rfind(';');
    ASSERT(one != std::string::npos, "no ;");
    ASSERT(two != std::string::npos, "no ;");
    std::string red = color.substr(0, one);
    std::string green = color.substr(one + 1, two - one - 1);
    std::string blue = color.substr(two + 1, color.size() - two);
    return osg::Vec4(atof(red.c_str()), atof(green.c_str()), atof(blue.c_str()), 1.0);
}


//----------------------------------------------------------------------------
// function setTransparent
//----------------------------------------------------------------------------
/// sets transparent color for given drawable
//----------------------------------------------------------------------------
static void setTransparent(osg::Drawable *pDrawable, ///< drawable to apply color to
                           const osg::Vec4& color)   ///< color to apply
{
    ASSERT(pDrawable, "no drawable");

    osg::StateSet *pStateSet = pDrawable->getOrCreateStateSet();
    pStateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
    pStateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
    osg::ref_ptr<osg::Material> pMaterial = new osg::Material();
    pMaterial->setDiffuse(osg::Material::FRONT_AND_BACK, color);
    pMaterial->setSpecular(osg::Material::FRONT_AND_BACK, color);
    pMaterial->setTransparency(osg::Material::FRONT_AND_BACK, 0.5);
    pStateSet->setAttribute(pMaterial.get());
}


//----------------------------------------------------------------------------
// function isTransparent
//----------------------------------------------------------------------------
/// checks if specified color is transparent (color name starts with '-')
/** @return true if transparent */
//----------------------------------------------------------------------------
static bool isTransparent(std::string *pColor) ///< color name to check
{
    ASSERT(pColor, "no color");

    if ((*pColor)[0] == '-')
    {
        *pColor = pColor->substr(1, pColor->size() - 1);
        return true;
    }
    return false;
}


//----------------------------------------------------------------------------
// function setColor
//----------------------------------------------------------------------------
/// sets color of object (transparent if name starts with '-')
//----------------------------------------------------------------------------
static void setColor(osg::ShapeDrawable *pDrawable, ///< drawable to apply color to
                     const std::string& color)      ///< color to apply
{
    ASSERT(pDrawable, "no drawable");

    std::string c = color;
    if (isTransparent(&c))
    {
        setTransparent(pDrawable, getColor(c));
    }
    pDrawable->setColor(getColor(c));
}


//----------------------------------------------------------------------------
// function forceWireFrameModeOff
//----------------------------------------------------------------------------
/// forces node and its children to be drawn filled (overrides parents state)
/** http://www.3drealtimesimulation.com/osg/osg_faq_1.htm#f49 */
//----------------------------------------------------------------------------
static void forceWireFrameModeOff(osg::Node* pNode)
{
    ASSERT(pNode, "no node");

    // grab state set of node, create one if needed
    osg::StateSet* pState = pNode->getOrCreateStateSet();

    // retrieve polygon mode of state set, create one if needed
    osg::PolygonMode *pPolyModeObj;
    pPolyModeObj = dynamic_cast<osg::PolygonMode*>(pState->getAttribute(osg::StateAttribute::POLYGONMODE));
    if (!pPolyModeObj)
    {
        pPolyModeObj = new osg::PolygonMode;
        pState->setAttribute(pPolyModeObj);
    }

    //  set state to Filled
    pPolyModeObj->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
}


//----------------------------------------------------------------------------
// function makeText
//----------------------------------------------------------------------------
/// makes text for display
/** @return geode that contains text drawable */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Geode> makeText(const osg::Vec3& pos,     ///< position of text
                                         float size,               ///< size of text
                                         const std::string& text,  ///< text to display
                                         const std::string &color) ///< color of text
{
    osg::ref_ptr<osgText::Text> pText = new  osgText::Text;
    pText->setPosition(pos);
    pText->setCharacterSize(size);
    pText->setAlignment(osgText::Text::CENTER_TOP);
    pText->setAutoRotateToScreen(true);
    //pText->setCharacterSizeMode(osgText::Text::OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT);
    pText->setText(text);
    pText->setColor(getColor(color));
    osg::ref_ptr<osg::Geode> pGeode = new osg::Geode();
    forceWireFrameModeOff(pGeode);
    pGeode->addDrawable(pText.get());
    return pGeode.get();
}


//----------------------------------------------------------------------------
// function makeAxis
//----------------------------------------------------------------------------
/// creates labeled axis
/** adapted from osgautotransform example in OpenSceneGraph distribution */
/** @return group with axis, ticks and labels */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Node> makeAxis(const osg::Vec3& s,       ///< start point
                                        const osg::Vec3& e,       ///< end point
                                        const std::string &color, ///< color of axis
                                        const std::string &name,  ///< name label
                                        float length,             ///< length of axis
                                        float ticks)              ///< number of ticks
{
    // create group for axis plus label
    osg::ref_ptr<osg::Group> group = new osg::Group;

    // fill polyline with axis points
    osg::Vec3 dv = e - s;
    dv /= ticks;
    osg::Vec3 pos = s;
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    for (int i = 0; i <= ticks - 1; ++i)
    {
        char buffer[20];
        sprintf(buffer, "%d", int(i * length / ticks));
        group->addChild(makeText(osg::Vec3(pos), length * 0.07, buffer, color).get());
        vertices->push_back(pos);
        pos += dv;
    }
    vertices->push_back(pos);

    // add label to group
    group->addChild(makeText(osg::Vec3(pos), length * 0.07, name, color).get());

    // make color array
    osg::ref_ptr<osg::Vec4Array> colorArray = new osg::Vec4Array;
    colorArray->push_back(getColor(color));

    // make geometry object and add vertices and color
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, vertices->size()));
    geom->setVertexArray(vertices.get());
    geom->setColorArray(colorArray.get());
    geom->setColorBinding(osg::Geometry::BIND_OVERALL);

    // turn off lighting for object of no width
    osg::StateSet *state = geom->getOrCreateStateSet();
    state->setMode(GL_LIGHTING, osg::StateAttribute::PROTECTED | osg::StateAttribute::OFF);

    // make geode object and add geometry
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable(geom.get());

    // add geometry to group
    group->addChild(geode.get());
    return group.get();
}


//----------------------------------------------------------------------------
// function makeDotObject
//----------------------------------------------------------------------------
/// makes object at one given coordinate for display
/** @return drawable that contains shape */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Drawable> makeDotObject(const std::string &type,  ///< sphere, box, cone, cylinder or capsule
                                                 const osg::Vec3 &xyz,     ///< center location
                                                 float size,               ///< diameter
                                                 const std::string &color) ///< color
{
    osg::ref_ptr<osg::Shape> pShape = NULL;
    if (type == SPHERE)
    {
        pShape = new osg::Sphere(xyz, size / 2);          // radius
    }
    else if (type == BOX)
    {
        pShape = new osg::Box(xyz, size);                 // side
    }
    else if (type == CONE)
    {
        pShape = new osg::Cone(xyz, size / 2, size);      // radius , height
    }
    else if (type == CYLINDER)
    {
        pShape = new osg::Cylinder(xyz, size / 2, size);  // radius , height
    }
    else if (type == CAPSULE)
    {
        pShape = new osg::Capsule(xyz, size / 2, size);   // radius , height
    }
    else
    {
        ASSERTFALSE();
    }
    osg::ref_ptr<osg::ShapeDrawable> pDrawable = new osg::ShapeDrawable(pShape.get());
    setColor(pDrawable.get(), color);
    return pDrawable.get();
}


//----------------------------------------------------------------------------
// function makeLine
//----------------------------------------------------------------------------
/// makes line with no thickness for display
/** @return drawable that contains line */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Drawable> makeLine(const osg::Vec3 &xyz1,    ///< start point
                                            const osg::Vec3 &xyz2,    ///< end point
                                            const std::string &color) ///< color
{
    // make vertices array
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back(xyz1);
    vertices->push_back(xyz2);

    // make colors array
    osg::ref_ptr<osg::Vec4Array> colorArray = new osg::Vec4Array;
    colorArray->push_back(getColor(color));

    // add both to a geometry object
    osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
    geometry->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, vertices->size()));
    geometry->setVertexArray(vertices.get());
    geometry->setColorArray(colorArray.get());
    geometry->setColorBinding(osg::Geometry::BIND_OVERALL);

    // turn off lighting for object of no width
    osg::StateSet *state = geometry->getOrCreateStateSet();
    state->setMode(GL_LIGHTING, osg::StateAttribute::PROTECTED | osg::StateAttribute::OFF);

    return geometry.get();
}


//----------------------------------------------------------------------------
// function makeStickObject
//----------------------------------------------------------------------------
/// makes object between two given coordinates for display
/** @return drawable that contains shape */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Drawable> makeStickObject(const std::string &type,  ///< cylinder or capsule or box
                                                   const osg::Vec3 &xyz1,    ///< start position
                                                   const osg::Vec3 &xyz2,    ///< end position
                                                   float diameter,           ///< diameter of stick
                                                   const std::string &color) ///< color
{
    if (diameter < 0.0000000001)
    {
        return makeLine(xyz1, xyz2, color);
    }

    // OpenSceneGraph always creates a cylinder parallel to the Z axis!
    osg::Vec3 zAxis(0.0, 0.0, 1.0);

    // desired direction of cylinder
    osg::Vec3 finalAxis(xyz1 - xyz2);

    // desired center and length of cylinder
    osg::Vec3 xyzm = (xyz1 + xyz2) / 2;
    float length = finalAxis.length();

    // create quaternion for the necessary rotation
    osg::Quat quat;
    quat.makeRotate(zAxis, finalAxis);

    // create cylinder/capsule at computed center with desired radius and computed length along z-axis,
    // rotate it and encapsulate it in a drawable object
    osg::ref_ptr<osg::ShapeDrawable> pDrawable = NULL;
    if (type == CYLINDER)
    {
        osg::ref_ptr<osg::Cylinder> pShape = new osg::Cylinder(xyzm, diameter / 2, length);
        pShape->setRotation(quat);
        pDrawable = new osg::ShapeDrawable(pShape.get());
    }
    else if (type == CAPSULE)
    {
        osg::ref_ptr<osg::Capsule> pShape = new osg::Capsule(xyzm, diameter / 2, length - diameter);
        pShape->setRotation(quat);
        pDrawable = new osg::ShapeDrawable(pShape.get());
    }
    else if (type == BOX)
    {
        osg::ref_ptr<osg::Box> pShape = new osg::Box(xyzm, diameter, diameter, length);
        pShape->setRotation(quat);
        pDrawable = new osg::ShapeDrawable(pShape.get());
    }
    else
    {
        ASSERTFALSE();
    }

    // set color of drawable
    setColor(pDrawable.get(), color);

    return pDrawable.get();
}


//----------------------------------------------------------------------------
// function makeRectangle
//----------------------------------------------------------------------------
/// short description
/// makes rectangle with given thickness for display
/** the coordinates must be given in sequence along the perimeter with a short side first
  * @return drawable that contains shape */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Drawable> makeRectangle(const osg::Vec3 &xyz1,   ///< coordinate 1
                                                 const osg::Vec3 &xyz2,   ///< coordinate 2
                                                 const osg::Vec3 &xyz3,   ///< coordinate 3
                                                 const osg::Vec3 &xyz4,   ///< coordinate 4
                                                 float size,              ///< thickness
                                                 const std::string &color)///< color
{
    // center of box
    osg::Vec3 xyzm = (xyz1 + xyz2 + xyz3 + xyz4) / 4;

    // lengths of sides
    osg::Vec3 side1 = xyz2 - xyz1;
    osg::Vec3 side2 = xyz4 - xyz1;
    float length1 = side1.length();
    float length2 = side2.length();

    // create quaternion for the necessary rotation
    osg::Vec3 xAxis(1.0, 0.0, 0.0);
    osg::Vec3 yAxis(0.0, 1.0, 0.0);
    osg::Quat quat1;
    quat1.makeRotate(xAxis, side1);
    osg::Quat quat2;
    quat2.makeRotate(yAxis, side2);
    quat2 = quat1 * quat2;

    // create box at computed center with short side along x-axis, long side along y-axis, size along z-axis
    osg::ref_ptr<osg::Box> pShape = new osg::Box(xyzm, length1, length2, size);

    // apply computed rotation to rectangle
    pShape->setRotation(quat1);

    // encapsulate in drawable object and set its color
    osg::ref_ptr<osg::ShapeDrawable> pDrawable = new osg::ShapeDrawable(pShape.get());
    setColor(pDrawable.get(), color);

    return pDrawable.get();
}


//----------------------------------------------------------------------------
// function makePolygon
//----------------------------------------------------------------------------
/// makes flat polygon for display
/** @return drawable that contains shape */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Drawable> makePolygon(const std::vector<osg::Vec3> &xyz, ///< vertices
                                               const std::string &color)          ///< color
{
    // specify coords for polygon in a clockwise direction
    osg::Vec3Array* vertices = new osg::Vec3Array();
    float x = 0.0;
    float y = 0.0;
    float z = 0.0;
    for (int i = 0; i < (int)xyz.size(); i++)
    {
        vertices->push_back(xyz[i]);
        x += xyz[i].x();
        y += xyz[i].y();
        z += xyz[i].z();
    }
    unsigned int numCoords = vertices->size();
    osg::Vec3 xyzm(x / numCoords, y / numCoords, z / numCoords);

    // Compute normal as being proportional to projected areas of polygon onto the yz,
    // xz, and xy planes. Also compute centroid as representative point on the plane
    osg::Vec3 normal(0.0f, 0.0f, 0.0f);
    for (unsigned int i = vertices->size() - 1, j = 0; j < vertices->size(); i = j, j++)
    {
        osg::Vec3 vi = (*vertices)[i];
        osg::Vec3 vj = (*vertices)[j];
        normal[0] += (vi[1] - vj[1]) * (vi[2] + vj[2]); // projection on yz
        normal[1] += (vi[2] - vj[2]) * (vi[0] + vj[0]); // projection on xz
        normal[2] += (vi[0] - vj[0]) * (vi[1] + vj[1]); // projection on xy
    }
    // Normalize normal
    normal.normalize();

    // create Geometry object to store all vertices
    osg::ref_ptr<osg::Geometry> pGeometry = new osg::Geometry();
    pGeometry->setVertexArray(vertices);

    // add color to Geometry object
    osg::ref_ptr<osg::Vec4Array> clrs = new osg::Vec4Array;
    std::string c = color;
    if (isTransparent(&c))
    {
        setTransparent(pGeometry.get(), getColor(c));
    }
    clrs->push_back(getColor(c));
    pGeometry->setColorArray(clrs.get());
    pGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);

    // add normal to Geometry object (this is for lighting only)
    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back(normal);
    pGeometry->setNormalArray(normals.get());
    pGeometry->setNormalBinding(osg::Geometry::BIND_OVERALL);

    // tell Geometry object about interpretation of vertices
    pGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, 0, numCoords));

    return pGeometry.get();
}


// TODO merge with makePolygon ?
//----------------------------------------------------------------------------
// function makePanel
//----------------------------------------------------------------------------
/// makes panel for display
/** @return drawable that contains shape */
//----------------------------------------------------------------------------
static osg::ref_ptr<osg::Drawable> makePanel(const std::vector<osg::Vec3> &xyz, ///< vertices
                                             const osg::Vec3& surfacevector,    ///< normal
                                             const std::string &color)          ///< color
{
    osg::Vec3Array* vertices = new osg::Vec3Array();
    int numCoords = xyz.size();
    for (int i = 0; i < numCoords; i++)
    {
        vertices->push_back(xyz[i]);
    }
    osg::Vec3 normal(surfacevector);
    normal.normalize();

    // create Geometry object to store all vertices
    osg::ref_ptr<osg::Geometry> pGeometry = new osg::Geometry();
    pGeometry->setVertexArray(vertices);

    // add color to Geometry object
    osg::ref_ptr<osg::Vec4Array> clrs = new osg::Vec4Array;
    std::string c = color;
    if (isTransparent(&c))
    {
        setTransparent(pGeometry.get(), getColor(c));
    }
    clrs->push_back(getColor(c));
    pGeometry->setColorArray(clrs.get());
    pGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);

    // add normal to Geometry object (this is for lighting only)
    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back(normal);
    pGeometry->setNormalArray(normals.get());
    pGeometry->setNormalBinding(osg::Geometry::BIND_OVERALL);

    // tell Geometry object about interpretation of vertices
    pGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, 0, numCoords));

    return pGeometry.get();
}


//----------------------------------------------------------------------------
// function initialize
//----------------------------------------------------------------------------
/// initializes some arrays
//----------------------------------------------------------------------------
static void initialize()
{
    // fill two arrays with contants
    shapes.push_back(SPHERE);
    shapes.push_back(BOX);
    shapes.push_back(CONE);
    shapes.push_back(CYLINDER);
    shapes.push_back(CAPSULE);
    shapes.push_back(RECTANGLE);
    shapes.push_back(POLYGONE);
    colors[BLACK]   = osg::Vec4(0,0,0,1);
    colors[WHITE]   = osg::Vec4(1,1,1,1);
    colors[RED]     = osg::Vec4(1,0,0,1);
    colors[GREEN]   = osg::Vec4(0,1,0,1);
    colors[BLUE]    = osg::Vec4(0,0,1,1);
    colors[CYAN]    = osg::Vec4(0,1,1,1);
    colors[MAGENTA] = osg::Vec4(1,0,1,1);
    colors[YELLOW]  = osg::Vec4(1,1,0,1);
    colors[ORANGE]  = osg::Vec4(1.0, 0.5, 0.0, 1.0);
    colors[GREY]    = osg::Vec4(0.5, 0.5, 0.5, 1.0);
}


// --- helper classes ----------------------------------------------------------

namespace osgviewer
{

// NOSTYLE : for brevity's sake, these classes contain inline function definitions

//----------------------------------------------------------------------------
// class TextBox
//----------------------------------------------------------------------------
/// displays 2d text that is not influenced by the camera position
/** class TextBox from http://www.cs.clemson.edu/~malloy/courses/3dgames-2007/tutor/web/text/text.html */
//----------------------------------------------------------------------------
class TextBox
{
public:

    /// constructor
    TextBox(int width,  ///< static width, does not change
            int height) ///< static height, does not change
        : m_matrixTransform(new osg::MatrixTransform())
        , m_projectionMatrix(new osg::Projection)
        , m_textGeode(new osg::Geode())
        , m_text(new osgText::Text())
    {
        m_matrixTransform->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
        m_matrixTransform->addChild(m_projectionMatrix);
        m_projectionMatrix->setMatrix(osg::Matrix::ortho2D(0, width, 0, height));
        m_projectionMatrix->addChild(m_textGeode);
        m_textGeode->addDrawable(m_text);
        m_text->setAxisAlignment(osgText::Text::SCREEN);
        m_text->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
        osg::StateSet *state = m_textGeode->getOrCreateStateSet();
        state->setMode(GL_LIGHTING, osg::StateAttribute::PROTECTED | osg::StateAttribute::OFF);
        m_text->setDataVariance(osg::Object::DYNAMIC);
        forceWireFrameModeOff(m_textGeode);
        setPosition(osg::Vec3d(10, 10, 0));
        setColor(osg::Vec4(1, 1, 1, 1));
        setTextSize(48);
    }

    /// destructor
    virtual ~TextBox()                    { }

    /// displays specified text
    void setText(const std::string& t)    { m_text->setText(t); }

    /// uses specified font for text display
    void setFont(const std::string& font) { m_text->setFont(font); }

    /// uses specified color for text display
    void setColor(osg::Vec4d color)       { m_text->setColor(color); }

    /// uses specified size for text display
    void setTextSize(unsigned int size)   { m_text->setCharacterSize(size); }

    /// puts text to display at specified position
    void setPosition(osg::Vec3 position) { m_text->setPosition(position); }

    /// returns height of text
    float getTextSize() const             { return m_text->getCharacterHeight(); }

    /// returns displayed text
    std::string getText() const           { return m_text->getText().createUTF8EncodedString(); }

    /// sets size of entire box
    void setSize(int width, int height)   { m_projectionMatrix->setMatrix(osg::Matrix::ortho2D(0,width,0,height)); }

    /// returns object as a Group
    osg::Group* getGroup() const          { return m_matrixTransform; }

private:

    osg::MatrixTransform *m_matrixTransform;  ///< PAD
    osg::Projection      *m_projectionMatrix; ///< projection matrix to use
    osg::Geode           *m_textGeode;        ///< geode to attach text to
    osgText::Text        *m_text;             ///< text object itself

    /// @cond nevercall
    TextBox();
    TextBox(const TextBox& s);
    TextBox& operator=(const TextBox& s);
    /// @endcond
};


//----------------------------------------------------------------------------
// class ScreenCaptureHandler
//----------------------------------------------------------------------------
/// enables automatic capturing of each frame
/** see http://lists.openscenegraph.org/pipermail/osg-users-openscenegraph.org/2008-December/020968.html */
//----------------------------------------------------------------------------
class ScreenCaptureHandler : public osgViewer::ScreenCaptureHandler
{
public:

    /// contructor, defines image file name prefix
    ScreenCaptureHandler(const std::string &prefix) ///< name prefix of png sequence
        : osgViewer::ScreenCaptureHandler(new WriteToFile(prefix, "png",
             osgViewer::ScreenCaptureHandler::WriteToFile::SEQUENTIAL_NUMBER))
    {
    }

    /// captures specified viewer's views on the next frame
    virtual void captureNextFrame(osgViewer::ViewerBase& viewer) ///< viewer to capture
    {
        addCallbackToViewer(viewer);
    }

private:

    /// @cond nevercall
    ScreenCaptureHandler();
    ScreenCaptureHandler(const ScreenCaptureHandler& s);
    ScreenCaptureHandler& operator=(const ScreenCaptureHandler& s);
    /// @endcond
};


//----------------------------------------------------------------------------
// class MouseEventHandler
//----------------------------------------------------------------------------
/// derived mouse handler for trapping mouse input
//----------------------------------------------------------------------------
class MouseEventHandler : public osgGA::GUIEventHandler
{
public:

    /// constructor
    MouseEventHandler(OsgViewer *pViewer) ///< handles mouse events of this viewer
        : m_pViewer(pViewer)
        , m_X(0)
        , m_Y(0)
    {
    }

    /// handles mouse clicks
    /** if a click handler is defined in the viewer, calls this on a mouse click */
    // TODO handle drag ?
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        osgGA::GUIEventAdapter::EventType eventType = ea.getEventType();
        switch (eventType)
        {
            case osgGA::GUIEventAdapter::PUSH :
            case osgGA::GUIEventAdapter::MOVE :
            {
                // record mouse location for button press and move events
                m_X = ea.getX();
                m_Y = ea.getY();
                return true;
            }
            case osgGA::GUIEventAdapter::RELEASE :
            {
                float xx = ea.getX();
                float yy = ea.getY();

                // try to find picked object
                osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
                ASSERT(view, "invalid view");
                osgUtil::LineSegmentIntersector::Intersections intersections;
                if (view->computeIntersections(xx, yy, intersections))
                {
                    for (osgUtil::LineSegmentIntersector::Intersections::iterator hitr = intersections.begin();
                         hitr != intersections.end();
                         ++hitr)
                    {
                        std::string foundName = findNode(hitr->nodePath.back());
                        if (!foundName.empty())
                        {
                            osg::Vec3 position = hitr->getWorldIntersectPoint();
                            bool ctrl = (ea.getModKeyMask() &
                                osgGA::GUIEventAdapter::MODKEY_LEFT_CTRL) ==
                                osgGA::GUIEventAdapter::MODKEY_LEFT_CTRL;
                            switch (ea.getButton())
                            {
                                case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON:
                                    if (m_pViewer->m_clickedLeft)
                                    {
                                        if (m_pViewer->m_clickedLeft(position, foundName, ctrl))
                                        {
                                            return true;
                                        }
                                    }
                                    break;
                                case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON:
                                    if (m_pViewer->m_clickedRight)
                                    {
                                        if (m_pViewer->m_clickedRight(position, foundName, ctrl))
                                        {
                                            return true;
                                        }
                                    }
                                    break;
                                default:;
                            }
                        }
                    }
                }
                return false;
            }
            default:
                return false;
        }
    }

private:

    /// @cond nevercall
    MouseEventHandler();
    MouseEventHandler(const MouseEventHandler& s);
    MouseEventHandler& operator=(const MouseEventHandler& s);
    /// @endcond

    OsgViewer *m_pViewer;            ///< pointer to owning viewer
    float m_X;                       ///< remembers mouse x-coordinate
    float m_Y;                       ///< remembers mouse y-coordinate

    /// searches object in object list based on parent geode
    std::string findNode(const osg::Node* pNode)
    {
        for (OsgViewer::ObjectList::iterator it =
             m_pViewer->m_Objects.begin(); it != m_pViewer->m_Objects.end(); ++it)
        {
            const osg::Geode* pGeode = dynamic_cast<const osg::Geode *>(pNode);
            if (pGeode)
            {
                const osg::Node* pParent = pNode->getParent(0);
                const osg::PositionAttitudeTransform* pPat = dynamic_cast<const osg::PositionAttitudeTransform*>(pParent);
                if (pPat && dynamic_cast<const osg::PositionAttitudeTransform*>(it->second) == pPat)
                {
                    return it->first;
                }
            }
        }
        return std::string("");
    }

};


//----------------------------------------------------------------------------
// class KeyboardEventHandler
//----------------------------------------------------------------------------
/// derived keyboard handler for trapping keyboard input
//----------------------------------------------------------------------------
class KeyboardEventHandler : public osgGA::GUIEventHandler
{
public:

    /// constructor
    KeyboardEventHandler(OsgViewer *pViewer) ///< handles keyboard events of this viewer
    : m_pViewer(pViewer)
    { }

    /// handles some keystrokes
    /** x     -> project along x axis
      * y     -> project along y axis
      * z     -> project along z axis
      * space -> back to initial viewpoint
      * tab   -> toggles light direction
      * c     -> takes screenshot
      * m     -> toggles screencast
      * o     -> saves osg file
      * w     -> toggles wireframe view
      * f     -> toggles fullscreen
      * s     -> saves viewing position
      * v     -> loads viewing position */
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& ad)
    {
        if (ea.getEventType() == osgGA::GUIEventAdapter::CLOSE_WINDOW)
        {
            m_pViewer->quit();
        }
        else if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
        {
            int key = ea.getKey();
            if (m_pViewer->m_keyPressed)
            {
                if (m_pViewer->m_keyPressed(key))
                {
                    return true;
                }
            }
            if (key == osgGA::GUIEventAdapter::KEY_Tab)
            {
                m_pViewer->toggleLightDirection();
                return true;
            }
            key = tolower(key);
            if (strchr("xyz", key))
            {
                m_pViewer->setEye(key);
                return true;
            }
            else if (key == ' ')
            {
                m_pViewer->resetInitialHome();
                return true;
            }
            else if (key == 'm')
            {
                m_pViewer->toggleScreenCast();
                return true;
            }
            else if (key == 'o')
            {
                m_pViewer->writeOsgFile();
                return true;
            }
            else if (key == 's')
            {
                m_pViewer->saveView();
                return true;
            }
            else if (key == 'v')
            {
                m_pViewer->loadView();
                return true;
            }
        }
        // 'l', c', 'w' and 'f' are handled by base class
        return false;
    }

private:

    /// @cond nevercall
    KeyboardEventHandler();
    KeyboardEventHandler(const KeyboardEventHandler& s);
    KeyboardEventHandler& operator=(const KeyboardEventHandler& s);
    /// @endcond

    OsgViewer *m_pViewer; ///< pointer to owning viewer
};


//----------------------------------------------------------------------------
// class GridCallback
//----------------------------------------------------------------------------
/// derived heightfield update callback handler
//----------------------------------------------------------------------------
class GridCallback : public osg::NodeCallback
{
public:
    /// constructor
    GridCallback(OsgViewer *pViewer) ///< handles grid events of this viewer
    : m_pViewer(pViewer)
    { }

    /// updates scene
    virtual void operator()(osg::Node *pNode, osg::NodeVisitor *pNv)
    {
        if (pNv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
        {
            m_pViewer->updateHeightFields();
        }
        traverse(pNode, pNv);
    }

private:

    /// @cond nevercall
    GridCallback();
    GridCallback(const GridCallback& s);
    GridCallback& operator=(const GridCallback& s);
    /// @endcond

    OsgViewer *m_pViewer; ///< pointer to owning viewer
};


/// global list of all OsgViewers in the program
osgviewer::OsgViewer::VIEWERLIST osgviewer::OsgViewer::m_ViewerList;


//----------------------------------------------------------------------------
// function OsgViewer::OsgViewer
//----------------------------------------------------------------------------
/// constructor
/** constructs window, camera, event handlers and starts viewer */
//----------------------------------------------------------------------------
OsgViewer::OsgViewer(const std::string& title) ///< window title
    : m_xviewDistFactor(1.0)
    , m_yviewDistFactor(1.0)
    , m_zviewDistFactor(1.0)
    , m_zscale(1.0)
    , m_pRoot(new osg::Group)
    , m_pScene(new osg::Group)
    , m_pCamera(0)
    , m_pLight(0)
    , m_pTextBox(new TextBox(WIDTH, HEIGHT))
    , m_pHeightField(0)
    , m_pHeightFieldDrawable(0)
    , m_pHeightFieldArray(0)
    , m_xmin(0)
    , m_ymin(0)
    , m_xmax(0)
    , m_ymax(0)
    , m_caption(title)
    , m_keyPressed(0)
    , m_clickedLeft(0)
    , m_clickedRight(0)
    , m_animation(0)
    , m_eye(0.0, 0.0, 0.0)
    , m_center(0.0, 0.0, 0.0)
    , m_up(0.0, 0.0, 0.0)
    , m_distance(1000)
    , m_Objects()
    , m_slopfactor(1.0)
    , m_capture(false)
    , m_pScreenCaptureHandler(NULL)
    , m_quit(false)
{
    // add viewer to viewer list
    if (this->m_ViewerList.empty())
    {
        initialize();
    }
    m_ViewerList.push_back(this);

    // construct window and attach camera
    // (taken from osgwindows example in OpenSceneGraph distribution)
    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
    traits->x = 50;
    traits->y = 50;
    traits->width = WIDTH;
    traits->height = HEIGHT;
    traits->windowDecoration = true;
    traits->doubleBuffer = true;
    traits->sharedContext = 0;
    traits->windowName = m_caption;
    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    m_pCamera = camera.get();
    camera->setGraphicsContext(gc.get());
    camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
    GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
    camera->setDrawBuffer(buffer);
    camera->setReadBuffer(buffer);
    addSlave(camera.get(), osg::Matrixd::translate(0.0, 0.0, 0.0), osg::Matrixd());
    setCameraManipulator(new osgGA::TrackballManipulator());

    // attach event handlers for keystrokes and mouse clicks
    getEventHandlers().push_front(new MouseEventHandler(this));
    getEventHandlers().push_front(new KeyboardEventHandler(this));

    // attach event handlers for screencasts and screenshots
    m_pScreenCaptureHandler = new ScreenCaptureHandler("movie");
    getEventHandlers().push_front(m_pScreenCaptureHandler);
    getEventHandlers().push_front(new ScreenCaptureHandler("screenshot"));

    // add state manipulator for wireframe display
    getEventHandlers().push_front(new osgGA::StateSetManipulator(getCamera()->getOrCreateStateSet()));

    // add full screen handler
    getEventHandlers().push_front(new osgViewer::WindowSizeHandler);

    // start viewer
    realize();
}


//----------------------------------------------------------------------------
// function OsgViewer::~OsgViewer
//----------------------------------------------------------------------------
/// destructor
/** erases viewer list */
//----------------------------------------------------------------------------
OsgViewer::~OsgViewer()
{
    VIEWERLIST::iterator it = std::find(m_ViewerList.begin(), m_ViewerList.end(), this);
    if (it != m_ViewerList.end())
    {
        m_ViewerList.erase(it);
    }
    delete m_pTextBox;
}


//----------------------------------------------------------------------------
// function OsgViewer::setCaptionAttributes
//----------------------------------------------------------------------------
/// sets position, size and color of caption text
//----------------------------------------------------------------------------
void OsgViewer::setCaptionAttributes(const osg::Vec3 &position, ///< position (from bottom left)
                                     float size,                ///< character height
                                     const std::string& color)  ///< character color
{
    m_pTextBox->setPosition(position);
    m_pTextBox->setColor(getColor(color));
    m_pTextBox->setTextSize(size);
}


//----------------------------------------------------------------------------
// function OsgViewer::setCaption
//----------------------------------------------------------------------------
/// sets bottom left text in window
//----------------------------------------------------------------------------
void OsgViewer::setCaption(const std::string &caption) ///< string to display
{
    m_pTextBox->setText(caption.c_str());
}


//----------------------------------------------------------------------------
// function OsgViewer::setScale
//----------------------------------------------------------------------------
/// sets exaggerated scale in specified direction
//----------------------------------------------------------------------------
void OsgViewer::setScale(char dir,     ///< direction ('x', 'y' or 'z'
                         float factor) ///< scaling factor
{
    switch (dir)
    {
        case 'x' : ASSERT(false, "x scaling not yet implemented");
                   break;
        case 'y' : ASSERT(false, "y scaling not yet implemented");
                   break;
        case 'z' : m_zscale = factor;
                   break;
        default  : ASSERTFALSE();
    }
}


//----------------------------------------------------------------------------
// function OsgViewer::setViewingDistanceFactors
//----------------------------------------------------------------------------
/// sets factors to use in viewpoint determination
//----------------------------------------------------------------------------
void OsgViewer::setViewingDistanceFactors(const osg::Vec3& xyz) ///< modifies viewpoint
{
    m_xviewDistFactor = xyz.x();
    m_yviewDistFactor = xyz.y();
    m_zviewDistFactor = xyz.z();
}


//----------------------------------------------------------------------------
// function OsgViewer::getHome
//----------------------------------------------------------------------------
/// returns camera home position
//----------------------------------------------------------------------------
void OsgViewer::getHome(osg::Vec3d* pEye,    ///< viewpoint
                        osg::Vec3d* pCenter, ///< center of scene
                        osg::Vec3d* pUp)     ///< up direction
                        const
{
    ASSERT(pEye, "no eye");
    ASSERT(pCenter, "no center");
    ASSERT(pUp, "no up");

    super::getCameraManipulator()->getHomePosition(*pEye, *pCenter, *pUp);
    // needs osg::Vec3d !
}


//----------------------------------------------------------------------------
// function OsgViewer::setHome
//----------------------------------------------------------------------------
/// sets camera home position
//----------------------------------------------------------------------------
void OsgViewer::setHome(const osg::Vec3d& eye,    ///< viewpoint
                        const osg::Vec3d& center, ///< center of scene
                        const osg::Vec3d& up)     ///< up direction
{
    super::getCameraManipulator()->setHomePosition(eye, center, up);
    super::home();
}


//----------------------------------------------------------------------------
// function OsgViewer::setEye
//----------------------------------------------------------------------------
/// sets viewpoint along an axis
//----------------------------------------------------------------------------
void OsgViewer::setEye(char dir) ///< 'x', 'y' or 'z' direction
{
    resetInitialHome();
    osg::Vec3d center(0, 0, 0);
    osg::Vec3d eye(0, 0, 0);
    osg::Vec3d up(0, 0, 0);
    getHome(&eye, &center, &up);
    switch (dir)
    {
        case 'x' : eye.set(-m_distance, center.y(), center.z());
                   break;
        case 'y' : eye.set(center.x(), -m_distance, center.z());
                   break;
        case 'z' : eye.set(center.x(), center.y(), m_distance);
                   break;
        default  : ASSERTFALSE();
    }
    setHome(eye, center, up);
}


//----------------------------------------------------------------------------
// function OsgViewer::setInitialHome
//----------------------------------------------------------------------------
/// sets initial camera home position
//----------------------------------------------------------------------------
void OsgViewer::setInitialHome(const osg::Vec3d& eye,    ///< viewpoint
                               const osg::Vec3d& center, ///< center of scene
                               const osg::Vec3d& up)     ///< up direction
{
    m_eye    = eye;
    m_center = center;
    m_up     = up;
    resetInitialHome();
}


//----------------------------------------------------------------------------
// function OsgViewer::resetInitialHome
//----------------------------------------------------------------------------
/// resets initial camera home position
//----------------------------------------------------------------------------
void OsgViewer::resetInitialHome()
{
    ASSERT(m_eye != m_center, "call setInitialHome() first!");
    setHome(m_eye, m_center, m_up);
}


//----------------------------------------------------------------------------
// function OsgViewer::setXyzHomeDistance
//----------------------------------------------------------------------------
/// sets viewing distance for viewing along x, y or z axis
//----------------------------------------------------------------------------
void OsgViewer::setXyzHomeDistance(float distance) ///< viewing distance
{
    m_distance = distance;
}


//----------------------------------------------------------------------------
// function OsgViewer::getSlopFactor
//----------------------------------------------------------------------------
/// returns slop factor for clicking
/** @return slop factor */
//----------------------------------------------------------------------------
float OsgViewer::getSlopFactor() const
{
    return m_slopfactor;
}


//----------------------------------------------------------------------------
// function OsgViewer::start
//----------------------------------------------------------------------------
/// sets root of scene and creates light
//----------------------------------------------------------------------------
void OsgViewer::start()
{
    // add geode and textbox to root
    m_pRoot->addChild(m_pScene);
    m_pRoot->addChild(m_pTextBox->getGroup());
    super::setSceneData(m_pRoot);

    // sets viewing distance
    osg::BoundingSphere box = m_pRoot->getBound();
    float distance = box.radius() * 2;
    osg::Vec3d eye(box.center().x() - distance * m_xviewDistFactor,
                   box.center().y() - distance * m_yviewDistFactor,
                   box.center().z() + distance * m_zviewDistFactor);
    setInitialHome(eye, box.center(), osg::Vec3(0, 0, 1));
    setXyzHomeDistance(box.radius() * 4);

    // add light to root
    osg::Vec4 lightpos;
    lightpos.set(-distance, 0.0, distance, 1.0);
//    lightpos.set(-0.5f, 0.5f, 0.8f, 0.0f);
//    lightpos.set(0.5f, 0.25f, 0.8f, 0.0f);
    osg::ref_ptr<osg::LightSource> ls = new osg::LightSource;
    ls->getLight()->setPosition(lightpos);
    ls->getLight()->setDiffuse(osg::Vec4(1.0, 1.0, 1.0, 1.0));
    ls->getLight()->setAmbient(osg::Vec4(0.6, 0.6, 0.6, 1.0));
    ls->getLight()->setSpecular(osg::Vec4(0.1, 0.1, 0.1, 1.0));
    m_pRoot->addChild(ls.get());
    m_pLight = ls.get();

    // ease mouse manipulation
    m_slopfactor = 0.0001 * box.radius();
    //printSceneGraph(m_pRoot, 0);

    // first plot
    plot();
}


//----------------------------------------------------------------------------
// function OsgViewer::run
//----------------------------------------------------------------------------
/// keeps viewer running freely
/** overrides standard osgViewer::run()
 * @return 1 to stop running */
//----------------------------------------------------------------------------
int OsgViewer::run()
{
    if (!m_pRoot->getNumChildren())
    {
        start();
    }
    
    for (;;)
    {
        if (m_animation)
        {
            m_animation();
        }
        if (m_capture)
        {
            m_pScreenCaptureHandler->captureNextFrame(*this);
        }
        for (VIEWERLIST::const_iterator it = m_ViewerList.begin(); it != m_ViewerList.end(); ++it)
        {
            microsleep(50000); // 0.05 seconds delay to conserve processor time
            if ((*it)->done())
            {
                return 1;
            }
            (*it)->frame();
        }
        if (m_capture)
        {
            microsleep(200000); // to enable capture handler to save png, otherwise frames are missed
        }
        if (m_quit)
        {
            return 1;
        }
    }
}


//----------------------------------------------------------------------------
// function OsgViewer::plot
//----------------------------------------------------------------------------
/// redraws all windows once
/** @return false if window is closed */
//----------------------------------------------------------------------------
bool OsgViewer::plot()
{
    if (m_capture)
    {
        m_pScreenCaptureHandler->captureNextFrame(*this);
    }
    for (VIEWERLIST::const_iterator it = m_ViewerList.begin(); it != m_ViewerList.end(); ++it)
    {
        (*it)->frame();
    }
    if (m_capture)
    {
        microsleep(200000); // to enable capture handler to save png, otherwise frames are missed
    }
    if (m_quit)
    {
        return false;
    }
    return true;
}


//----------------------------------------------------------------------------
// function OsgViewer::setKeyHandler
//----------------------------------------------------------------------------
/// defines handler for key presses
//----------------------------------------------------------------------------
void OsgViewer::setKeyHandler(KeyHandler handler) ///< pointer to handler or NULL
{
    m_keyPressed = handler;
}


//----------------------------------------------------------------------------
// function OsgViewer::setLeftClickHandler
//----------------------------------------------------------------------------
/// defines handler for clicking left
//----------------------------------------------------------------------------
void OsgViewer::setLeftClickHandler(ClickHandler handler) ///< pointer to handler or NULL
{
    m_clickedLeft = handler;
}


//----------------------------------------------------------------------------
// function OsgViewer::setRightClickHandler
//----------------------------------------------------------------------------
/// defines handler for clicking right
//----------------------------------------------------------------------------
void OsgViewer::setRightClickHandler(ClickHandler handler) ///< pointer to handler or NULL
{
    m_clickedRight = handler;
}


//----------------------------------------------------------------------------
// function OsgViewer::setAnimationHandler
//----------------------------------------------------------------------------
/// defines handler for animation (called every update)
//----------------------------------------------------------------------------
void OsgViewer::setAnimationHandler(AnimationHandler handler) ///< pointer to handler or NULL
{
    m_animation = handler;
}


//----------------------------------------------------------------------------
// function OsgViewer::addObject
//----------------------------------------------------------------------------
/// adds object to drawlist
//----------------------------------------------------------------------------
void OsgViewer::addObject(const std::string &name, ///< name of object (used as index)
                          const std::string &type, ///< sphere, box, cone, cylinder or capsule
                          const std::string &color,///< color name or "r;g;b"
                          float diameter,          ///< diameter, side or height
                          const osg::Vec3& xyz1,   ///< start or center point
                          const osg::Vec3& xyz2)   ///< end point (may be omitted)
{
    osg::ref_ptr<osg::Drawable> pDrawable;
    if (xyz2 == defpos /* && type == SPHERE || type == BOX || type == CONE*/)
    {
        pDrawable = makeDotObject(type, xyz1, diameter, color);
    }
    else if (type == CYLINDER || type == CAPSULE || type == BOX)
    {
        pDrawable = makeStickObject(type, xyz1, xyz2, diameter, color);
    }
    else
    {
        ASSERTFALSE();
    }
    osg::Geode *pGeode= new osg::Geode();
    pGeode->addDrawable(pDrawable.get());
    osg::PositionAttitudeTransform *pPat = new osg::PositionAttitudeTransform();
    pPat->addChild(pGeode);
    m_pScene->addChild(pPat);
    m_Objects.insert(std::make_pair(name, pPat));
}


//----------------------------------------------------------------------------
// function OsgViewer::addPolygon
//----------------------------------------------------------------------------
/// adds flat polygon to drawlist
//----------------------------------------------------------------------------
void OsgViewer::addPolygon(const std::string &name,           ///< name of object (used as index)
                           const std::string &color,          ///< color name or "r;g;b"
                           const std::vector<osg::Vec3>& xyz) ///< vertices
{
    ASSERT(xyz.size() >= 3, "n must be at least 3");
    osg::ref_ptr<osg::Drawable> pDrawable = makePolygon(xyz, color);
    osg::Geode *pGeode= new osg::Geode();
    pGeode->addDrawable(pDrawable.get());
    osg::PositionAttitudeTransform *pPat = new osg::PositionAttitudeTransform();
    pPat->addChild(pGeode);
    m_pScene->addChild(pPat);
    m_Objects.insert(std::make_pair(name, pPat));
}


//----------------------------------------------------------------------------
// function OsgViewer::addRectangle
//----------------------------------------------------------------------------
/// adds rectangle with specified thickness to drawlist
/** the coordinates must be given in sequence along the perimeter with a short side first */
//----------------------------------------------------------------------------
void OsgViewer::addRectangle(const std::string &name,  ///< name of object (used as index)
                             const std::string &color, ///< color name or "r;g;b"
                             float thickness,          ///< guess what this is
                             const osg::Vec3 &xyz1,    ////< point 1
                             const osg::Vec3 &xyz2,    ////< point 2
                             const osg::Vec3 &xyz3,    ////< point 3
                             const osg::Vec3 &xyz4)    ////< point 4
{
    osg::ref_ptr<osg::Drawable> pDrawable = makeRectangle(xyz1, xyz2, xyz3, xyz4, thickness, color);
    osg::Geode *pGeode= new osg::Geode();
    pGeode->addDrawable(pDrawable.get());
    osg::PositionAttitudeTransform *pPat = new osg::PositionAttitudeTransform();
    pPat->addChild(pGeode);
    m_pScene->addChild(pPat);
    m_Objects.insert(std::make_pair(name, pPat));
}


// TODO for the moment only one heightfield can be added and it is not added to the drawlist, so cannot be removed
//----------------------------------------------------------------------------
// function OsgViewer::addDynamicSurface
//----------------------------------------------------------------------------
/// adds heightfield 
//----------------------------------------------------------------------------
void OsgViewer::addDynamicSurface(const std::string &name,      ///< name of object (used as index)
                                  const std::string& color,     ///< color name or "r;g;b"
                                  const Array2D<float> *pField, ///< pointer to heightfield data
                                  int xmin,                     ///< lowest x-coordinate index
                                  int ymin,                     ///< lowest y-coordinate index
                                  int xmax,                     ///< highest x-coordinate index
                                  int ymax,                     ///< highest y-coordinate index
                                  float dx,                     ///< x spacing
                                  float dy)                     ///< y spacing
{
    ASSERT(pField, "no field");
    ASSERT(!m_pHeightField, "already heightfield present"); // TODO accomodate heightfields in drawlist

    m_xmin = xmin;
    m_ymin = ymin;
    m_xmax = xmax;
    m_ymax = ymax;

    m_pHeightFieldArray = pField;

    // create heightfield
    m_pHeightField = new osg::HeightField;
    m_pHeightFieldDrawable = new osg::ShapeDrawable(m_pHeightField);
    m_pHeightField->setName(name);
    m_pHeightFieldDrawable->setName(name + "Drawable");
    m_pHeightField->allocate(xmax - xmin + 1, ymax - ymin + 1);
    m_pHeightField->setOrigin(osg::Vec3(0, 0, 0));
    m_pHeightField->setXInterval(dx);
    m_pHeightField->setYInterval(dy);
    m_pHeightField->setDataVariance(osg::Object::DYNAMIC);
    m_pHeightFieldDrawable->setDataVariance(osg::Object::DYNAMIC);
    m_distance = std::max((xmax - xmin + 1) * dx, (ymax - ymin + 1) * dy);

    // setup callback and add to geode
    m_pScene->setDataVariance(osg::Object::DYNAMIC);
    m_pScene->setUpdateCallback(new GridCallback(this));
    osg::Geode *pGeode = new osg::Geode();
    pGeode->addDrawable(m_pHeightFieldDrawable);

    // add color to geode
    std::string c = color;
    bool transparent = isTransparent(&c);
    osg::StateSet *pStateSet = pGeode->getOrCreateStateSet();
    if (transparent)
    {
        pStateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
        pStateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
    }
    osg::ref_ptr<osg::Material> pMaterial = new osg::Material();
    pMaterial->setDiffuse(osg::Material::FRONT_AND_BACK, getColor(c));
    pMaterial->setSpecular(osg::Material::FRONT_AND_BACK, getColor(c));
    if (transparent)
    {
        pMaterial->setTransparency(osg::Material::FRONT_AND_BACK, 0.5);
    }
    pStateSet->setAttribute(pMaterial.get());
    
    // add geode to pat, add pat to scene and remember object
    osg::PositionAttitudeTransform *pPat = new osg::PositionAttitudeTransform();
    pPat->addChild(pGeode);
    m_pScene->addChild(pPat);
    m_Objects.insert(std::make_pair(name, pPat));
}


//----------------------------------------------------------------------------
// function OsgViewer::addPanelRoot
//----------------------------------------------------------------------------
/// adds panel attachment point to drawlist
//----------------------------------------------------------------------------
void OsgViewer::addPanelRoot(const std::string &name) ///< name of object (used as index)
{
    osg::Geode *pGeode = new osg::Geode();
    osg::PositionAttitudeTransform *pPat = new osg::PositionAttitudeTransform();
    pPat->addChild(pGeode);
    m_pScene->addChild(pPat);
    m_Objects.insert(std::make_pair(name, pPat));
}


//----------------------------------------------------------------------------
// function OsgViewer::addPanel
//----------------------------------------------------------------------------
/// adds panel to already present panel root
//----------------------------------------------------------------------------
void OsgViewer::addPanel(const std::string &name,                ///< name of panel root
                         const std::string &color,               ///< color name or "r;g;b"
                         const std::vector<osg::Vec3>& vertices, ///< corner points
                         const osg::Vec3& normal)                ///< normal on panel
{
    ObjectList::iterator iObject = m_Objects.find(name);
    if (iObject != m_Objects.end())
    {
        osg::Node* pObject = iObject->second;
        osg::PositionAttitudeTransform* pPat = dynamic_cast<osg::PositionAttitudeTransform*>(pObject);
        if (pPat)
        {
            osg::ref_ptr<osg::Drawable> pDrawable = makePanel(vertices, normal, color);
            osg::Geode *pGeode = dynamic_cast<osg::Geode*>(pPat->getChild(0));
            pGeode->addDrawable(pDrawable.get());
            // beware: there is no way of asserting that this geode was created as panel root!
        }
    }
}


//----------------------------------------------------------------------------
// function OsgViewer::addText
//----------------------------------------------------------------------------
/// adds text to drawlist
//----------------------------------------------------------------------------
void OsgViewer::addText(const std::string &name,  ///< name of object (used as index)
                        const std::string &color, ///< color name or "r;g;b"
                        float size,               ///< character height
                        const std::string& text,  ///< text to display
                        const osg::Vec3& xyz)     ///< middle bottom point of text
{
    osg::ref_ptr<osg::Geode> geode = makeText(xyz, size, text, color);
    osg::PositionAttitudeTransform *pPat = new osg::PositionAttitudeTransform();
    pPat->addChild(geode.get());
    m_pScene->addChild(pPat);
    m_Objects.insert(std::make_pair(name, pPat));
}


// TODO make axes removable as well (put in object list)
//----------------------------------------------------------------------------
// function OsgViewer::addAxes
//----------------------------------------------------------------------------
/// adds three axes with specified orientation and length
//----------------------------------------------------------------------------
void OsgViewer::addAxes(const osg::Vec3& orientation, ///< directions of axes (1 or -1)
                        float length,                 ///< length
                        float ticks)                  ///< number of tick marks
{
    osg::Vec3 point;
    point = osg::Vec3(orientation.x() * length, 0.0, 0.0);
    m_pScene->addChild(makeAxis(osg::Vec3(0.0, 0.0, 0.0), point, RED, "X", length, ticks).get());
    point = osg::Vec3(0.0, orientation.y() * length, 0.0);
    m_pScene->addChild(makeAxis(osg::Vec3(0.0, 0.0, 0.0), point, BLUE, "Y", length, ticks).get());
    point = osg::Vec3(0.0, 0.0, orientation.z() * length);
    m_pScene->addChild(makeAxis(osg::Vec3(0.0, 0.0, 0.0), point, YELLOW, "Z", length, ticks).get());
}


//----------------------------------------------------------------------------
// function OsgViewer::moveObject
//----------------------------------------------------------------------------
/// moves specified object to specified location
//----------------------------------------------------------------------------
void OsgViewer::moveObject(const std::string &name, ///< name of object (used as index)
                           const osg::Vec3& xyz)    ///< new position
{
    ObjectList::iterator iObject = m_Objects.find(name);
    if (iObject != m_Objects.end())
    {
        osg::Node* pObject = iObject->second;
        osg::PositionAttitudeTransform* pPat = dynamic_cast<osg::PositionAttitudeTransform*>(pObject);
        if (pPat)
        {
            pPat->setPosition(xyz);
        }
    }
}


// TODO not tested !!!
//----------------------------------------------------------------------------
// function OsgViewer::rotateObject
//----------------------------------------------------------------------------
/// rotates object to specified roll,pitch,yaw
//----------------------------------------------------------------------------
void OsgViewer::rotateObject(const std::string &name, ///< name of object (used as index)
                             const osg::Vec3& xyz)    ///< roll, pitch, yaw
{
    ObjectList::iterator iObject = m_Objects.find(name);
    if (iObject != m_Objects.end())
    {
        osg::Node* pObject = iObject->second;
        osg::PositionAttitudeTransform* pPat = dynamic_cast<osg::PositionAttitudeTransform*>(pObject);
        if (pPat)
        {
            osg::Quat quat(xyz.x(), osg::Vec3(1.0, 0.0, 0.0),
                           xyz.y(), osg::Vec3(0.0, 1.0, 0.0),
                           xyz.z(), osg::Vec3(0.0, 0.0, 1.0));
            pPat->setAttitude(quat);
        }
    }
}


//----------------------------------------------------------------------------
// function OsgViewer::removeObject
//----------------------------------------------------------------------------
/// removes object from drawlist
/** @return true if object was in drawlist */
//----------------------------------------------------------------------------
bool OsgViewer::removeObject(const std::string &name) ///< name of object (used as index)
{
    bool found = false;
    for (ObjectList::iterator iObject = m_Objects.begin(); iObject != m_Objects.end(); ++iObject)
    {
        if (iObject->first == name)
        {
            osg::Node* pObject = iObject->second;
            m_pScene->removeChild(pObject);
            found = true;
        }
    }
    /*int n = */ m_Objects.erase(name);
    return found;
}


//----------------------------------------------------------------------------
// function OsgViewer::removeAllObjects
//----------------------------------------------------------------------------
/// removes all objects from drawlist
//----------------------------------------------------------------------------
void OsgViewer::removeAllObjects()
{
    m_Objects.clear();
    m_pScene->removeChildren(0, m_pScene->getNumChildren());
}


// TODO for all heightfields in drawlist
//----------------------------------------------------------------------------
// function OsgViewer::updateHeightFields
//----------------------------------------------------------------------------
/// copies the plot data to the height fields
//----------------------------------------------------------------------------
void OsgViewer::updateHeightFields()
{
//    int nx = m_pHeightFieldArray->dim1();
//    int ny = m_pHeightFieldArray->dim2();
    for (int y = m_ymin; y <= m_ymax; ++y)
    {
        for (int x = m_xmin; x <= m_xmax; ++x)
        {
            m_pHeightField->setHeight(x - m_xmin, y - m_ymin, m_zscale * (*m_pHeightFieldArray)[x][y]);
        }
    }

    // set the heightfield drawables to dirty (otherwise you don't see anything move!)
    m_pHeightFieldDrawable->dirtyBound();
    m_pHeightFieldDrawable->dirtyDisplayList();
}


//----------------------------------------------------------------------------
// function OsgViewer::toggleScreenCast
//----------------------------------------------------------------------------
/// toggles automatic screen dumps
//----------------------------------------------------------------------------
void OsgViewer::toggleScreenCast()
{
    m_capture = ! m_capture;
    printf("capture %s\n", m_capture ? "on" : "off");
}


//----------------------------------------------------------------------------
// function OsgViewer::toggleLightDirection
//----------------------------------------------------------------------------
/// toggles light direction left or right
//----------------------------------------------------------------------------
void OsgViewer::toggleLightDirection()
{
    osg::Vec4 lightpos = m_pLight->getLight()->getPosition();
    lightpos.set(-lightpos.x(), +lightpos.y(), +lightpos.z(), 1.0);
    m_pLight->getLight()->setPosition(lightpos);
}


//----------------------------------------------------------------------------
// function OsgViewer::isScreenCasting
//----------------------------------------------------------------------------
/// reports if screencasting is on
/** @return true if on */
//----------------------------------------------------------------------------
bool OsgViewer::isScreenCasting() const
{
    return m_capture;
}


//----------------------------------------------------------------------------
// function OsgViewer::writeOsgFile
//----------------------------------------------------------------------------
/// writes OpenSceneGraph osg file of current scene in current directory
//----------------------------------------------------------------------------
void OsgViewer::writeOsgFile()
{
    osgDB::writeNodeFile(*m_pRoot, m_caption + ".osg");
}


// -----------------------------------------------------------------------------
// function OsgViewer::saveView
// -----------------------------------------------------------------------------
/// saves viewing position
// -----------------------------------------------------------------------------
void OsgViewer::saveView()
{
    FILE *fp = fopen(viewfile, "w");
    if (fp)
    {
        osg::Vec3d eye, center, up;
        getCamera()->getViewMatrixAsLookAt(eye, center, up);
        fprintf(fp, "%G\n", eye.x());
        fprintf(fp, "%G\n", eye.y());
        fprintf(fp, "%G\n", eye.z());
        fprintf(fp, "%G\n", center.x());
        fprintf(fp, "%G\n", center.y());
        fprintf(fp, "%G\n", center.z());
        fprintf(fp, "%G\n", up.x());
        fprintf(fp, "%G\n", up.y());
        fprintf(fp, "%G\n", up.z());
        fclose(fp);
    }
    else
    {
        fprintf(stderr, "file %s not saved\n", viewfile);
    }
}


// -----------------------------------------------------------------------------
// function OsgViewer::loadView
// -----------------------------------------------------------------------------
/// load previously saved viewing position
// -----------------------------------------------------------------------------
void OsgViewer::loadView()
{
    FILE *fp = fopen(viewfile, "r");
    if (fp)
    {
        osg::Vec3d eye, center, up;
        double x, y, z;
        int ignore = 0;
        ignore = fscanf(fp, "%lf\n", &x);
        ignore = fscanf(fp, "%lf\n", &y);
        ignore = fscanf(fp, "%lf\n", &z);
        eye.set(x, y, z);
        ignore = fscanf(fp, "%lf\n", &x);
        ignore = fscanf(fp, "%lf\n", &y);
        ignore = fscanf(fp, "%lf\n", &z);
        center.set(x, y, z);
        ignore = fscanf(fp, "%lf\n", &x);
        ignore = fscanf(fp, "%lf\n", &y);
        ignore = fscanf(fp, "%lf\n", &z);
        up.set(x, y, z);
        setHome(eye, center, up);
        fclose(fp);
    }
    else
    {
        fprintf(stderr, "file %s not loaded\n", viewfile);
    }
}


//----------------------------------------------------------------------------
// function OsgViewer::quit
//----------------------------------------------------------------------------
/// sets quit flag
//----------------------------------------------------------------------------
void OsgViewer::quit()
{
    m_quit = true;
}


//----------------------------------------------------------------------------
// function OsgViewer::getBound
//----------------------------------------------------------------------------
/// returns bounding sphere of indicated object or entire graph
/** @return bounding sphere */
//----------------------------------------------------------------------------
const osg::BoundingSphere& OsgViewer::getBound(const std::string& name) const ///< name of object (used as index)
{
    static osg::BoundingSphere error;
    if (name.empty())
    {
        return m_pScene->getBound();
    }
    else
    {
        ObjectList::const_iterator iObject = m_Objects.find(name);
        if (iObject != m_Objects.end())
        {
            osg::Node* pObject = iObject->second;
            osg::PositionAttitudeTransform* pPat = dynamic_cast<osg::PositionAttitudeTransform*>(pObject);
            if (pPat)
            {
                return pPat->getBound();
            }
        }
    }
    return error;
}

} // namespace osgviewer


